package ccnu.agile.config.aspect;

import ccnu.agile.annotations.CApi;
import ccnu.agile.annotations.CParam;
import ccnu.agile.annotations.CParams;
import ccnu.agile.bean.entities.ProjectMemberRoleEntity;
import ccnu.agile.bean.entities.ResourceEntity;
import ccnu.agile.bean.entities.RoleResourceEntity;
import ccnu.agile.bean.exception.CAPDException;
import ccnu.agile.config.resthelp.RequestConcurrent;
import ccnu.agile.constant.System;
import ccnu.agile.enums.CMethod;
import ccnu.agile.enums.CResource;
import ccnu.agile.enums.PrivError;
import ccnu.agile.enums.PubError;
import ccnu.agile.service.ITokenService;
import ccnu.agile.strategy.paramcheck.ParamCheckContext;
import ccnu.agile.strategy.requestlog.RequestLogContext;
import ccnu.agile.strategy.restrequest.RestRequestContext;
import ccnu.agile.utils.FlakeUtils;
import ccnu.agile.utils.LogUtils;
import ccnu.agile.utils.RBACUtils;
import com.auth0.jwt.exceptions.TokenExpiredException;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;

/**
 * @Description: 全局接口拦截日志配置类
 * @Author: JreamY
 * @Date: 2021/10/21
 **/
@Aspect
@Component
public class WebLogAspect implements Ordered {

    @Autowired
    private RequestLogContext requestLogContext;

    @Autowired
    private RestRequestContext restRequestContext;

    @Autowired
    private ParamCheckContext paramCheckContext;

    @Autowired
    private RequestConcurrent requestConcurrent;

    @Autowired
    private ITokenService tokenService;

    @Pointcut("execution(public * ccnu.agile.controller.*.*(..))")
    public void webLog() {
    }

    @Before("webLog()")
    public void webLogDoBefore(JoinPoint point) throws NoSuchMethodException {

        // 生成transactionId
        MDC.put(System.MDC.TRANSACTION_ID, FlakeUtils.nextWorkerId());

        Class clazz = point.getTarget().getClass();
        String methodName = point.getSignature().getName();
        Class[] parameterTypes = ((MethodSignature) point.getSignature()).getParameterTypes();
        Method method = clazz.getMethod(methodName, parameterTypes);
        Annotation[] annotations = method.getDeclaredAnnotations();

        // 兼容RequestMapping 和 GetMapping | PostMapping | PutMapping | DeleteMapping
        CMethod cMethod = CMethod.POST;
        CApi cApi = null;
        boolean anony = false;
        CParams cParams = null;
        CParam cParam = null;

        for (Annotation annotation : annotations) {
            if (annotation instanceof RequestMapping) {
                RequestMethod requestMethod = ((RequestMapping) annotation).method()[0];
                cMethod = RequestMethod.GET.equals(requestMethod) ? CMethod.GET : CMethod.POST;
            }
            if (annotation instanceof GetMapping) {
                cMethod = CMethod.GET;
            }
            if (annotation instanceof CApi) {
                cApi = (CApi) annotation;
                anony = cApi.skipTokenCheck();
            }
            if (annotation instanceof CParams) {
                cParams = (CParams) annotation;
            }
            if (annotation instanceof CParam) {
                cParam = (CParam) annotation;
            }
        }

        if (cApi == null) {
            LogUtils.info(this, "接口未配置cApi");
            throw new CAPDException(PrivError.CPERMISSION_UNDEFINED);
        }

        if(cApi.skipTokenCheck() && !cApi.skipTokenCheck()) {
            LogUtils.info(this, "cpi配置有误");
            throw new CAPDException(PrivError.CPERMISSION_CONFIG_ERROR);
        }

        if (cApi.special()) {
            LogUtils.inter("POST: {}", cApi.specialRequestLog());
            return;
        }

        requestLogContext.requestLog(cMethod.getRequestLogStrategy(), point);

        // 统一参数：都放入Request中
        restRequestContext.restRequest(point, cMethod);

        // token校验
        if (!anony) {
            // 登录校验
            String token = (String) requestConcurrent.getCurrentRequest().get(System.RequestParam.TOKEN);
            if (StringUtils.isEmpty(token)) {
                LogUtils.info(this, "用户未登陆");
                throw new CAPDException(PubError.USER_NOT_LOGIN, PrivError.USER_NOT_LOGIN);
            }
            try {
                requestConcurrent.getCurrentRequest().setUserId(
                        tokenService.getValue(token, System.RequestParam.USER_ID, Integer.class)
                );
            } catch (TokenExpiredException e) {
                LogUtils.error("token已失效： {}", e);
                throw new CAPDException(PrivError.TOKEN_EXPIRE);
            } catch (Exception e) {
                LogUtils.error("token校验异常：{}", e);
                throw new CAPDException(PrivError.INVALID_TOKEN);
            }
        }

        // RBAC校验
        if (!cApi.skipRBAC()) {
            // 请求参数获取projectId
            String projectId = (String) requestConcurrent.getCurrentRequest().get(System.RequestParam.PROJECT_ID);
            // cPermission获取资源名称
            CResource cResource = cApi.resource();

            // tbl_user_project_role_info 查询roleId
            Integer userId = requestConcurrent.getCurrentRequest().getUserId();
            ProjectMemberRoleEntity pmrEntity = RBACUtils.findMemberRoleId(userId);
            if (pmrEntity == null) {
                LogUtils.info(this, "用户{}对应项目{}权限为空", userId, projectId);
                throw new CAPDException(PrivError.PROJECT_NOROLE);
            }

            // roleId查询tbl_role_resource_info 查询是否可以访问该资源
            Integer roleId = pmrEntity.getRoleId();
            List<RoleResourceEntity> rrList = RBACUtils.findRoleResourceList(roleId);
            if (CollectionUtils.isEmpty(rrList)) {
                LogUtils.info(this, "用户{}对应项目{}角色为{}, 未配置可访问资源列表", userId, projectId, roleId);
                throw new CAPDException(PrivError.PROJECT_ROLE_CONFIG_ERROR);
            }
            String resourceName = cResource.getName();
            ResourceEntity rEntity = RBACUtils.findNameResource(resourceName);
            if (rEntity == null) {
                LogUtils.info(this, "资源{}未找到配置", resourceName);
                throw new CAPDException(PrivError.RESOURCE_UNDEFINED);
            }
            boolean permissionAccess = false;
            for (RoleResourceEntity entity : rrList) {
                if (entity.getResourceId().equals(rEntity.getId())) {
                    permissionAccess = true;
                    break;
                }
            }
            if (!permissionAccess) {
                LogUtils.info(this, "用户{}对应项目{}角色为{}, 不可访问资源{}",
                        userId, projectId, roleId, resourceName);
                throw new CAPDException(PrivError.RESOURCE_PERMISSION);
            }
        }

        requestConcurrent.getCurrentRequest().setResourceName(cApi.resource().getName());

        // 参数校验
        if (cParams != null) {
            for (CParam temp : cParams.value()) {
                paramCheckContext.paramCheck(temp);
            }
        } else {
            if (cParam != null) {
                paramCheckContext.paramCheck(cParam);
            }
        }

    }

    @AfterReturning(returning = "response", pointcut = "webLog()")
    public void webLogDoAfterReturning(Object response) {

        requestConcurrent.removeCurrentRequest();
        LogUtils.inter("response: {}", response);
    }

    @Override
    public int getOrder() {
        return 1;
    }
}
